/**
 * 
 */
package com.xbang.core.web.interceptor;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.ServletActionContext;
import org.springframework.beans.factory.annotation.Autowired;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.OgnlContextState;
import com.opensymphony.xwork2.util.OgnlValueStack;
import com.opensymphony.xwork2.util.ValueStack;
import com.xbang.core.ChildSearchActionSupport;
import com.xbang.core.config.Note;
import com.xbang.core.dao.search.component.SearchComponent;
import com.xbang.core.dao.search.component.SearchContainer;
import com.xbang.core.dao.search.component.SearchContext;
import com.xbang.core.dao.search.component.SearchMapping;
import com.xbang.core.dao.support.GenericDaoSupport;
import com.xbang.core.struts.support.SearchActionSupport;
import com.xbang.core.utils.BeanUtils;
import com.xbang.core.utils.DateUtils;
import com.xbang.core.utils.ReflectionUtils;
import com.xbang.core.utils.SearchUtils;
import com.xbang.core.utils.TypeConverterUtils;

/**
 * @author dev.zhang
 */
public class SearchInterceptor extends AbstractInterceptor {

	private static final Log logger = LogFactory.getLog(SearchInterceptor.class);

	@Autowired
	private GenericDaoSupport genericDaoSupport;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com
	 * .opensymphony.xwork2.ActionInvocation)
	 */
	@Override
	public String intercept(ActionInvocation invocation) throws Exception {
		before(invocation);
		return invocation.invoke();
	}

	/**
	 * 
	 * @param invocation
	 * @throws Exception
	 */
	protected void before(ActionInvocation invocation) throws Exception {
		String searchConstantsKey = "search_constants_key";
		String urlTemplate = ServletActionContext.getRequest().getRequestURI();
		if (urlTemplate.split("/").length > 1) {
			String[] spliteUrl = urlTemplate.split("/");
			if (StringUtils.isNumeric(spliteUrl[spliteUrl.length - 1])) {
				searchConstantsKey = "search_constants_constants_key";
			} else if (spliteUrl.length > 2) {
				if (spliteUrl[spliteUrl.length - 1].toLowerCase().equals("search") && StringUtils.isNumeric(spliteUrl[spliteUrl.length - 2])) {
					searchConstantsKey = "search_constants_constants_key";
				}
			} else if (spliteUrl.length > 3) {
				if (spliteUrl[spliteUrl.length - 1].toLowerCase().equals("do") && spliteUrl[spliteUrl.length - 2].toLowerCase().equals("search")
						&& StringUtils.isNumeric(spliteUrl[spliteUrl.length - 3])) {
					searchConstantsKey = "search_constants_constants_key";
				}
			}

		}
		// determine whether the action extends SearchActionSupport
		if (invocation.getAction() instanceof SearchActionSupport) {
			// turns to SearchActionSupport
			SearchActionSupport action = (SearchActionSupport) invocation.getAction();

			// get ActionContext here
			ActionContext actionContext = invocation.getInvocationContext();

			// get all the parameters in request
			final Map parameters = actionContext.getParameters();

			// get request and session from ServletActionContext
			HttpServletRequest request = ServletActionContext.getRequest();
			HttpSession session = request.getSession();

			String searchCriteriaKey = getSearchCriteriaKey(request.getRequestURI());

			// searchCriteriaKey contains in httpSession, put the searchCriteria
			// into action
			if (session.getAttribute(searchCriteriaKey) != null) {
				Map<String, Object> searchCriteria = (Map<String, Object>) session.getAttribute(searchCriteriaKey);

				for (Iterator<Map.Entry<String, Object>> iter = searchCriteria.entrySet().iterator(); iter.hasNext();) {
					Map.Entry<String, Object> entry = iter.next();
					injectParameter(actionContext.getValueStack(), actionContext.getContextMap(), entry.getKey(), entry.getValue());
				}
			}

			// check parameters from request and reset searchComponent
			if (parameters != null) {

				SearchComponent searchComponent = action.getSearchComponent() == null ? new SearchComponent() : action.getSearchComponent();

				Map searchElements = new HashMap();

				// iterate every parameter and create searchComponent
				for (Iterator iter = parameters.entrySet().iterator(); iter.hasNext();) {
					Map.Entry entry = (Map.Entry) iter.next();
					String key = (String) entry.getKey(); // get key of
					// parameter
					String[] value;
					if ((entry.getValue() instanceof String[])) {
						value = (String[]) entry.getValue(); // get value of
						// parameter
					} else {
						value = new String[1];
						value[0] = String.valueOf(entry.getValue());
					}

					// add to searchElements when neccessary
					addToSearchElements(searchComponent, searchElements, key, value);
				}

				// create search component using searchElements
				searchComponent.addSearchElements(searchElements);
				// request.get

				// set back searchComponent to action
				String[] searchPaths = request.getRequestURI().substring(request.getRequestURI().indexOf("/", 1) + 1).split("/");
				String searchPath = "";
				String searchStartPath = "";
				if (searchPaths.length > 1) {
					searchPath = searchPath.concat(searchPaths[0]).concat(".").concat(searchPaths[1]).concat(".").concat("searchURI");
					searchStartPath = searchStartPath.concat(searchPaths[0]).concat("/").concat(searchPaths[1]).concat("/");
				}
				if (null != request.getSession().getAttribute(searchPath)) {
					if (request.getSession().getAttribute(searchConstantsKey) != null && request.getRequestURI().endsWith("search")) {
						if (!(invocation.getAction() instanceof ChildSearchActionSupport)) {

							request.getSession().removeAttribute(searchPath);
							request.getSession().removeAttribute(searchConstantsKey);
							request.getSession().removeAttribute(searchCriteriaKey);

							// searchStartPath
							if (!searchStartPath.equals("")) {
								Enumeration enumerations = request.getSession().getAttributeNames();
								while (enumerations.hasMoreElements()) {
									String key = enumerations.nextElement().toString();
									if (key.startsWith(searchStartPath)) {
										request.getSession().removeAttribute(key);
									}
								}
							}
							searchComponent.resetWhereSentence();
							searchComponent.resetGroupSentence();
							searchComponent.resetOrderSentence();
							request.getSession().setAttribute(searchPath, searchComponent);
						} else {
							SearchComponent tmpSearchComponent = (SearchComponent) request.getSession().getAttribute(searchPath);
							searchComponent.addSearchComponentContext(tmpSearchComponent);
							request.getSession().setAttribute(searchConstantsKey, searchPath);
							request.getSession().setAttribute(searchPath, searchComponent);
						}

					} else {

						SearchComponent tmpSearchComponent = (SearchComponent) request.getSession().getAttribute(searchPath);
						searchComponent.addSearchComponentContext(tmpSearchComponent);
						request.getSession().setAttribute(searchConstantsKey, searchPath);
						request.getSession().setAttribute(searchPath, searchComponent);
					}
				} else {
					request.getSession().setAttribute(searchConstantsKey, searchPath);
					request.getSession().setAttribute(searchPath, searchComponent);
				}

				action.setSearchComponent(searchComponent);
			}

			// register a new PreResultListener，restore the searchCriteria into
			// httpSession
			SearchComponent searchComponent = action.getSearchComponent() == null ? new SearchComponent() : action.getSearchComponent();
			invocation.addPreResultListener(new SearchCriteriaSessionStore(searchCriteriaKey));

			clearOrderSentences(searchComponent);
			clearGroupSentences(searchComponent);

			setSearcheComponentSearchParameterNote(invocation, searchComponent);

			action.setSearchComponent(searchComponent);
		}
	}

	/**
	 * set search component parameters.
	 * 
	 * @param searchComponent
	 */
	private void setSearcheComponentSearchParameterNote(ActionInvocation invocation, SearchComponent searchComponent) {
		SearchActionSupport searchAction = null;
		Action action = (Action) invocation.getAction();
		System.out.println(">>>>>>>>>>>>>>>>>>>>" + invocation.getProxy().getMethod());
		String methodString = invocation.getProxy().getMethod();
		
		
		System.out.println(">>>>>>>>>>>>>>>>>>>>" + action);
		if (invocation.getAction() instanceof SearchActionSupport) {
			// turns to SearchActionSupport
			searchAction = (SearchActionSupport) invocation.getAction();
		} else {
			return;
		}
		HttpServletRequest request = ServletActionContext.getRequest();
		if (!request.getRequestURI().endsWith("doSearch") && request.getRequestURI().endsWith("search")) {
			return;
		}
		Map<String, Object> orderParameters = searchComponent.getOrderParameters();
		Map<String, Object> whereParaemeters = searchComponent.getParameters();
		//
		if (null != orderParameters && !orderParameters.isEmpty()) {

		}

		if (null != whereParaemeters && !whereParaemeters.isEmpty()) {
			Map<String, String> whereSentences = searchComponent.getWhereSentences();
			for (Map.Entry<String, String> entry : whereSentences.entrySet()) {
				String whereKey = com.xbang.core.utils.StringUtils.generateParameterKey(entry.getKey());
				if (whereParaemeters.containsKey(whereKey)) {
					// /
					Object value = whereParaemeters.get(whereKey);

					
					///String methodString = invocation.getProxy().getMethod();
					String[] keys = entry.getKey().split("\\.");
					String operationKey = keys[0];
					if (keys.length > 1) {
						Class clazz = genericDaoSupport.getPersistMappingsByKey(keys[1]);
						if (clazz == null) {
							clazz =searchAction.getClazz(invocation.getProxy().getMethod());
							if (clazz == null) {
								break;
							}
						}
						String properties = com.xbang.core.utils.StringUtils.subString(entry.getKey(), "\\.", ".", 2);
						Field field = ReflectionUtils.getFieldType(clazz, properties, genericDaoSupport.getPersistMappings());
						if (field != null)
							setSearchParametersFieldNote(searchComponent, value, operationKey, field.getName(), field);
					}
				}
			}
		}
	}

	/**
	 * @param searchComponent
	 * @param value
	 * @param keys
	 * @param startIndex
	 * @param operationKey
	 * @param clazz
	 */
	private void setSearchParametersFieldNotes(SearchComponent searchComponent, Object value, String[] keys, int startIndex, String operationKey, Class clazz) {
		if (clazz != null) {
			Field[] fields = clazz.getDeclaredFields();
			for (Field field : fields) {
				boolean isSearch = isSearchExists(searchComponent, field);
				if (!isSearch) {
					continue;
				}
				if (genericDaoSupport.getPersistMappings().containsValue(field.getType())) {
					// ///
					Field[] subFields = field.getType().getDeclaredFields();
					for (Field subField : subFields) {
						boolean isTmpSearch = isSearchExists(searchComponent, subField);
						if (!isTmpSearch) {
							continue;
						}
						if (genericDaoSupport.getPersistMappings().containsValue(subField.getType())) {
							setSearchParametersFieldNotes(searchComponent, value, keys, startIndex, operationKey, subField.getType());
						} else if (subField.getName().equals(keys[startIndex + 1]) && subField.isAnnotationPresent(Note.class)) {
							setSearchParametersFieldNote(searchComponent, value, operationKey, keys[startIndex + 1], subField);
						}
					}

				} else {
					setSearchParametersFieldNote(searchComponent, value, operationKey, keys[startIndex], field);
				}
			}
		}
	}

	/**
	 * @param searchComponent
	 * @param field
	 * @return
	 */
	private boolean isSearchExists(SearchComponent searchComponent, Field field) {
		Set<String> tmpKeys = searchComponent.getWhereSentences().keySet();
		boolean isSearch = false;
		for (String tmpKey : tmpKeys) {
			String[] myTmpKeys = tmpKey.split("\\.");
			for (String myTmpKey : myTmpKeys) {
				if (myTmpKey.toLowerCase().equals(field.getName().toLowerCase())) {
					isSearch = true;
					break;
				}
			}
		}
		return isSearch;
	}

	/**
	 * @param searchComponent
	 * @param value
	 * @param keys
	 * @param fields
	 */
	private void setSearchParametersFieldNote(SearchComponent searchComponent, Object value, String operation, String fieldName, Field field) {
		if (field.getName().equals(fieldName) && field.isAnnotationPresent(Note.class)) {
			Note note = field.getAnnotation(Note.class);
			searchComponent.getSearchParameterNode().put("search".concat(operation).concat(field.getName()),
					note.value() + SearchUtils.OPERATION_MAPPING_NOTE.get(operation) + TypeConverterUtils.ConventParseString(field.getType(), value));
		}
	}

	/**
	 * 清理Order Sentences
	 * 
	 * @param searchComponent
	 */
	private void clearOrderSentences(SearchComponent searchComponent) {
		List<Map<String, String>> orders = searchComponent.getOrderSentences();
		List<Map<String, String>> newOrders = new ArrayList<Map<String, String>>();
		Map<String, String> tmpOrders = new HashMap<String, String>();
		if (orders != null && !orders.isEmpty()) {
			for (Map<String, String> orderMap : orders) {
				String key = orderMap.entrySet().iterator().next().getKey();
				String value = orderMap.entrySet().iterator().next().getValue();
				if (!tmpOrders.containsKey(key)) {
					tmpOrders.put(key, value);
					newOrders.add(orderMap);
				}
			}
		}
		searchComponent.getOrderSentences().clear();
		searchComponent.getOrderSentences().addAll(newOrders);
	}

	/**
	 * 清理group Sentences
	 * 
	 * @param searchComponent
	 */
	private void clearGroupSentences(SearchComponent searchComponent) {
		List<Map<String, String>> orders = searchComponent.getGroupSentences();
		List<Map<String, String>> newOrders = new ArrayList<Map<String, String>>();
		Map<String, String> tmpOrders = new HashMap<String, String>();
		if (orders != null && !orders.isEmpty()) {
			for (Map<String, String> orderMap : orders) {
				String key = orderMap.entrySet().iterator().next().getKey();
				String value = orderMap.entrySet().iterator().next().getValue();
				if (!tmpOrders.containsKey(key)) {
					tmpOrders.put(key, value);
					newOrders.add(orderMap);
				}
			}
		}
		 //newOrders.add(tmpOrders);
		searchComponent.getGroupSentences().clear();
		searchComponent.getGroupSentences().addAll(newOrders);
	}
	/**
	 * SearchCriteria session store
	 * 
	 * @author Owner
	 */
	public static class SearchCriteriaSessionStore implements PreResultListener {

		private String searchCriteriaKey;

		/**
		 * @param searchCriteriaKey
		 */
		public SearchCriteriaSessionStore(String searchCriteriaKey) {
			this.searchCriteriaKey = searchCriteriaKey;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * com.opensymphony.xwork2.interceptor.PreResultListener#beforeResult
		 * (com.opensymphony.xwork2.ActionInvocation, java.lang.String)
		 */
		public void beforeResult(ActionInvocation invocation, String resultCode) {

			if (invocation.getAction() instanceof SearchActionSupport) {

				SearchActionSupport action = (SearchActionSupport) invocation.getAction();
				action.resetSearchCriteria();

				// get request and session from ServletActionContext
				HttpServletRequest request = ServletActionContext.getRequest();
				HttpSession session = request.getSession();
				session.setAttribute(searchCriteriaKey, action.getSearchCriteria());

				// put into httpSession for those should be cleaned
				List<String> criteriaList;
				if (session.getAttribute("searchCriteriaList") != null) { // session
					// exists
					// searchCriteria
					// key.
					criteriaList = (List<String>) session.getAttribute("searchCriteriaList");
				} else {
					criteriaList = new ArrayList<String>();
				}
				criteriaList.add(searchCriteriaKey);
				session.setAttribute("searchCriteriaList", criteriaList);

				// set search parameters to request for page show
				SearchComponent searchComponent = action.getSearchComponent();
				request.setAttribute("searchParameters", searchComponent.getReturnedParameters());
				request.setAttribute("orderParameters", searchComponent.getOrderParameters());
				request.setAttribute("aliasOrderParameters", searchComponent.getAliasOrderParameters());
			}

		}

	}

	/**
	 * 
	 * @param requestURI
	 * @return
	 */
	private String getSearchCriteriaKey(String requestURI) {
		return requestURI.substring(requestURI.indexOf("/", 1) + 1) + "_SearchCriteria";
	}

	/**
	 * @param searchComponent
	 * @param searchElements
	 * @param key
	 * @param value
	 */
	private void addToSearchElements(SearchComponent searchComponent, Map searchElements, String key, String[] value) {
		// create a new searchContext
		SearchContext searchContext = new SearchContext(key);
		// only those not null inputs and satisfied key will be included in
		// search element
		if (searchContext.validate()) {
			// create real parameter here including type conversion
			Object parameter = null;
			if (searchContext.isOrderSentence() || searchContext.isGroupSentence()) {
				if (searchContext.isOrderSentence()) {
					searchComponent.resetOrderSentence();
				}
				parameter = value[0];
			} else {
				if (!BeanUtils.isArrayEmpty(value))
					parameter = createParameter(searchContext, value);
			}
			searchElements.put(key, parameter);
		}

	}

	/**
	 * @param searchContext
	 * @param value
	 * @return
	 */
	protected Object createParameter(SearchContext searchContext, String[] value) {
		SearchContainer searchContainer = new SearchContainer();

		if (value.length == 1) {
			return getValue(searchContext, value[0], searchContainer);
		} else {
			List parameters = new ArrayList();
			for (int i = 0; i < value.length; i++) {
				parameters.add(getValue(searchContext, value[i], searchContainer));
			}
			return parameters;
		}
	}

	/**
	 * 
	 * @param searchContext
	 * @param value
	 * @param searchContainer
	 * @return
	 */
	private Object getValue(SearchContext searchContext, String value, SearchContainer searchContainer) {

		String target = searchContext.getTarget(); // get search target, eg:
		// eq.user.name => user.name
		String context = searchContext.getContext(); // get search context, eg:
		// eq.user.name => user

		// create a searchContainer context
		if (!searchContainer.contains(context)) {
			searchContainer.set(context, SearchMapping.getPersistentClass(context));
		}

		// add % between value when operation is like
		if (searchContext.getOperation().toUpperCase().equals("LIKE")) {
			value = "%" + value + "%";
		}

		// using ognl support to determine the input parameter
		ValueStack stack = new OgnlValueStack();
		stack.push(searchContainer.getContext());

		if (searchContext.isObject()) {
			injectParameter(stack, searchContainer.getContext(), target + ".id", value);
		} else {
			injectParameter(stack, searchContainer.getContext(), target, value);
		}

		if (value.split("-").length == 3) {
			if (searchContext.getKey().startsWith("leq")) {
				Date tmpDate = DateUtils.parseDate(value.toString(), "yyyy-MM-dd");
//				tmpDate = DateUtils.addDay(tmpDate, 1);
				return tmpDate;
			} else
				return DateUtils.parseDate(value.toString(), "yyyy-MM-dd");
		}

		// return stack.findValue(target);
		return value;
	}

	/**
	 * 
	 * @param contextMap
	 * @param stack
	 * @param key
	 * @param parameter
	 */
	private void injectParameter(ValueStack valueStack, Map contextMap, String key, Object parameter) {
		try {
			OgnlContextState.setCreatingNullObjects(contextMap, true);
			OgnlContextState.setDenyMethodExecution(contextMap, true);
			OgnlContextState.setReportingConversionErrors(contextMap, true);

			// set parameter here using valueStack
			if (acceptableName(key)) {
				try {
					valueStack.setValue(key, parameter);
					logger.info(valueStack.getContext().get(key));
					if (null != valueStack.findValue(key)) {
						logger.info(">>>>>>>>>>>>>>>. into ... valueStack.findValue(key)" + valueStack.findValue(key));
					}
				} catch (RuntimeException e) {
					e.printStackTrace();
				}
			}

		} finally {
			OgnlContextState.setCreatingNullObjects(contextMap, false);
			OgnlContextState.setDenyMethodExecution(contextMap, false);
			OgnlContextState.setReportingConversionErrors(contextMap, false);
		}
	}

	private boolean acceptableName(String name) {
		if (name.indexOf('=') != -1 || name.indexOf(',') != -1 || name.indexOf('#') != -1 || name.indexOf(':') != -1) {
			return false;
		} else {
			return true;
		}
	}

}
